﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Text;
using Firecracker.Bootstrapper.Interception.Intercepts;
using Firecracker.Bootstrapper.Utils;
using Mono.Cecil;
using Mono.Cecil.Cil;

namespace Firecracker.Bootstrapper.Interception.Application
{
  public class InstructionDefinition
  {
    public TypeDefinition CallingType { get; set; }
    public Instruction Instruction { get; set; }
    public MethodDefinition CallingMethod { get; set; }
  }

  [Export(typeof(IInjector))]
  public class ConstructorInjector : IInjector
  {

    [Import]
    public IInterceptLocator Locator { get; set; }


    public bool ShouldApply(InstructionDefinition instructionDefinition)
    {
      return instructionDefinition.Instruction.OpCode == OpCodes.Newobj;
    }

    public void Apply(InstructionDefinition instructionDefinition)
    {
      var interceptionTargets = Locator.Locate();
      var call = (instructionDefinition.Instruction.Operand as MethodReference);

      var x = from t in interceptionTargets
              where call.DeclaringType.FullName == t.Target.FullName
              select t;

      var processor = instructionDefinition.CallingMethod.Body.GetILProcessor();
      foreach (var interceptType in x.ToList())
      {
        var currentConstructor = instructionDefinition.Instruction.Operand as MethodReference;
        var constructors = interceptType.Replacement.Methods.Where(p => p.Name == ".ctor");
        
        foreach (var constructor in constructors)
        {
          if (constructor.Parameters.Count == currentConstructor.Parameters.Count)
          {
            bool same = true;
            for (int i = 0; i < constructor.Parameters.Count; i++)
            {
              if (constructor.Parameters[i].ParameterType.Equals(currentConstructor.Parameters[i].ParameterType))
                same = false;
            }
            if (same)
            {
              var reference = instructionDefinition.CallingMethod.Module.Import(constructor);
              processor.Replace(instructionDefinition.Instruction, processor.Create(OpCodes.Newobj, reference));
            }
          }
        }
      }
    }
  }

  public class NoConstructorFoundException : Exception
  {
    private TypeDefinition _interceptType;
    private MethodReference _call;

    public NoConstructorFoundException(TypeReference reference, MethodReference call)
      : base()
    {
      _interceptType = reference.Resolve();
      _call = call;
    }

    public override string  Message
    {
	    get
	    {
	      return "Did not find a constructor in the Replacing class that matches the intercepting class.\n" +
	             "Call: " + _call + "\n" +
	             "Possible constructors: \n" + String.Join("\n", "\t" + _interceptType.Methods.Where(x => x.Name == ".ctor"));
	    }
    }
  }
}
